﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ThreadDispatcher.cs" company="JDML Consulting Ltd.">
//   Copyright 2011 Photon Project - http://photon.codeplex.com
// </copyright>
// <summary>
//   Defines the ThreadDispatcher type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Photon.Testing.Threading
{
    using System;
    using System.Collections.Generic;
    using System.Threading;

    public class ThreadDispatcher : IAsynchronousTestDispatcher
    {
        #region Fields

        private readonly Thread _thread;
        private readonly Queue<Action> _queue = new Queue<Action>();
        private volatile bool _isActive;

        #endregion

        public ThreadDispatcher(bool start)
        {
            _thread = new Thread(ThreadExecute) { Name = AsynchronousTest.DispatcherThreadName };
            if (start)
            {
                Start();
            }
        }

        public void SendOrPost(Action a)
        {
            lock (_queue)
            {
                _queue.Enqueue(a);
            }
        }

        public void SendOrPost(Delegate d, params object[] args)
        {
            SendOrPost(() => d.DynamicInvoke(args));
        }

        public bool CheckAccess()
        {
            return _thread != null && Thread.CurrentThread.ManagedThreadId == _thread.ManagedThreadId;
        }

        public void Start()
        {
            if (!_isActive)
            {
                _isActive = true;
                Error = null;
                _thread.Start();
            }
        }

        public void Stop(bool join)
        {
            _isActive = false;
            if (join)
            {
                Join();
            }
        }

        private void ThreadExecute()
        {
            SynchronizationContext.SetSynchronizationContext(
                   new AsynchronousTestSynchronizationContext(this));

            // process queue
            while (_isActive)
            {
                try
                {
                    DequeueAndDispatch();
                    Thread.Sleep(0);
                }
                catch (Exception e)
                {
                    HandleException(e);
                }
            }
        }

        private void HandleException(Exception e)
        {
            try
            {
                if (UnhandledException != null)
                {
                    UnhandledException(this, new UnhandledExceptionEventArgs(e, false));
                }
                else
                {
                    Error = e;
                }
            }
            catch (Exception ex)
            {
                Error = ex;
            }
            if (Error != null)
            {
                _isActive = false;
            }
        }

        private void DequeueAndDispatch()
        {
            var action = Dequeue();
            if (action != null)
            {
                action();
            }
            else
            {
                if (Idle != null)
                {
                    Idle(this, EventArgs.Empty);
                }
            }
        }

        private Action Dequeue()
        {
            lock (_queue)
            {
                return _queue.Count > 0 ? _queue.Dequeue() : null;
            }
        }

        public event EventHandler Idle;

        public Exception Error { get; private set; }

        public void Join()
        {
            if (CheckAccess())
            {
                throw new InvalidOperationException("Deadlock detected.");
            }

            if (_thread != null)
            {
                _thread.Join();
            }
        }

        public event UnhandledExceptionEventHandler UnhandledException;
    }
}